package com.axinfu.util.http;

import com.axinfu.util.DateUtil;
import com.axinfu.util.EmptyUtil;
import com.axinfu.util.FileUtil;
import com.axinfu.util.PathUtil;
import com.axinfu.util.http.response.HttpResponseResult;
import com.axinfu.util.http.response.ResponseHandler;
import com.axinfu.util.http.response.StringResponseHandler;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpTrace;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.cookie.Cookie;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * http(s)请求工具类
 *
 * @author zjn
 * @since 2022/3/23
 */
@SuppressWarnings("unused")
public class HttpRequester {

    private static final Logger log = LoggerFactory.getLogger(HttpRequester.class);

    private HttpClient httpClient;
    private RequestConfig requestConfig;
    private CookieStore cookieStore;

    private String httpMethod;
    private String url;
    private Map<String, String> queryParamMap;
    private Map<String, String> headerMap;
    private HttpEntity bodyEntity;

    private ResponseHandler<? extends HttpResponseResult> responseHandler;

    private Boolean printLog = false;
    private Boolean printFile = false;
    private String printFilePath = PathUtil.getRootClassPath();

    public HttpRequester(HttpClient httpClient, RequestConfig requestConfig) {
        this.httpClient = httpClient;
        this.requestConfig = requestConfig;
    }

    public HttpRequester(HttpClient httpClient, RequestConfig requestConfig, CookieStore cookieStore) {
        this.httpClient = httpClient;
        this.requestConfig = requestConfig;
        this.cookieStore = cookieStore;
    }

    /**
     * 创建对象
     *
     * @param httpClient    httpClient
     * @param requestConfig requestConfig
     * @return HttpRequester
     */
    public static HttpRequester create(HttpClient httpClient, RequestConfig requestConfig) {
        return new HttpRequester(httpClient, requestConfig).reset();
    }

    /**
     * 创建对象
     *
     * @param httpClient    httpClient
     * @param requestConfig requestConfig
     * @param cookieStore   cookieStore
     * @return HttpRequester
     */
    public static HttpRequester create(HttpClient httpClient, RequestConfig requestConfig, CookieStore cookieStore) {
        return new HttpRequester(httpClient, requestConfig, cookieStore).reset();
    }

    /**
     * 重置
     *
     * @return HttpRequester 重置后的HttpRequester
     */
    public HttpRequester reset() {
        httpMethod = HttpGet.METHOD_NAME;
        url = null;
        queryParamMap = new LinkedHashMap<>();
        headerMap = new HashMap<>(1);
        bodyEntity = null;

        responseHandler = new StringResponseHandler();

        printLog = false;
        printFile = false;
        printFilePath = PathUtil.getRootClassPath();

        return this;
    }

    /**
     * 添加url参数
     *
     * @param key   名称
     * @param value 值
     * @return 自身
     */
    public HttpRequester addQueryParam(String key, String value) {
        if (EmptyUtil.isNotEmpty(key)) {
            this.queryParamMap.put(key, value);
        }
        return this;
    }

    /**
     * 添加多个url参数
     *
     * @param queryParams 参数map
     * @return 自身
     */
    public HttpRequester addQueryParams(Map<String, String> queryParams) {
        if (EmptyUtil.isNotEmpty(queryParams)) {
            this.queryParamMap.putAll(queryParams);
        }
        return this;
    }

    /**
     * 添加header
     *
     * @param key   名称
     * @param value 值
     * @return 自身
     */
    public HttpRequester addHeader(String key, String value) {
        if (EmptyUtil.isNotEmpty(key)) {
            this.headerMap.put(key, value);
        }
        return this;
    }

    /**
     * 添加多个header
     *
     * @param headers header参数map
     * @return 自身
     */
    public HttpRequester addHeaders(Map<String, String> headers) {
        if (EmptyUtil.isNotEmpty(headers)) {
            this.headerMap.putAll(headers);
        }
        return this;
    }

    /**
     * 构建get请求
     *
     * @param url url
     * @return 自身
     */
    public HttpRequester get(String url) {
        setHttpMethod(HttpGet.METHOD_NAME);
        setUrl(url);
        return this;
    }

    /**
     * 构建post请求
     *
     * @param url url
     * @return 自身
     */
    public HttpRequester post(String url) {
        setHttpMethod(HttpPost.METHOD_NAME);
        setUrl(url);
        return this;
    }

    /**
     * 构建put请求
     *
     * @param url url
     * @return 自身
     */
    public HttpRequester put(String url) {
        setHttpMethod(HttpPut.METHOD_NAME);
        setUrl(url);
        return this;
    }

    /**
     * 构建head请求
     *
     * @param url url
     * @return 自身
     */
    public HttpRequester head(String url) {
        setHttpMethod(HttpHead.METHOD_NAME);
        setUrl(url);
        return this;
    }

    /**
     * 构建delete请求
     *
     * @param url url
     * @return 自身
     */
    public HttpRequester delete(String url) {
        setHttpMethod(HttpDelete.METHOD_NAME);
        setUrl(url);
        return this;
    }

    /**
     * 构建patch请求
     *
     * @param url url
     * @return 自身
     */
    public HttpRequester patch(String url) {
        setHttpMethod(HttpPatch.METHOD_NAME);
        setUrl(url);
        return this;
    }

    /**
     * 构建options请求
     *
     * @param url url
     * @return 自身
     */
    public HttpRequester options(String url) {
        setHttpMethod(HttpOptions.METHOD_NAME);
        setUrl(url);
        return this;
    }

    /**
     * 构建trace请求
     *
     * @param url url
     * @return 自身
     */
    public HttpRequester trace(String url) {
        setHttpMethod(HttpTrace.METHOD_NAME);
        setUrl(url);
        return this;
    }

    /**
     * 构建request对象
     *
     * @return request对象
     */
    private HttpRequestBase getHttpUriRequest() {
        if (EmptyUtil.isEmpty(this.url)) {
            throw new RuntimeException("url cannot be empty");
        }

        this.url = HttpUtil.urlBuildQueryString(this.url, this.queryParamMap);

        switch (this.httpMethod) {
            case HttpPost.METHOD_NAME:
                HttpPost httpPost = new HttpPost(url);
                httpPost.setEntity(bodyEntity);
                return httpPost;
            case HttpPut.METHOD_NAME:
                HttpPut httpPut = new HttpPut(url);
                httpPut.setEntity(bodyEntity);
                return httpPut;
            case HttpHead.METHOD_NAME:
                return new HttpHead(url);
            case HttpDelete.METHOD_NAME:
                return new HttpDelete(url);
            case HttpPatch.METHOD_NAME:
                HttpPatch httpPatch = new HttpPatch(url);
                httpPatch.setEntity(bodyEntity);
                return httpPatch;
            case HttpOptions.METHOD_NAME:
                return new HttpOptions(url);
            case HttpTrace.METHOD_NAME:
                return new HttpTrace(url);
            default:
                return new HttpGet(url);
        }
    }

    /**
     * 执行请求
     *
     * @return 请求结果
     */
    public HttpResponseResult execute() {
        try {
            HttpRequestBase request = getHttpUriRequest();
            request.setConfig(requestConfig);
            for (Map.Entry<String, String> header : headerMap.entrySet()) {
                request.addHeader(header.getKey(), header.getValue());
            }
            HttpResponse response = httpClient.execute(request);
            HttpResponseResult httpResponseResult = responseHandler.handler(request, response);
            print(request, response);
            return httpResponseResult;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 打印请求日志
     *
     * @param request  请求对象
     * @param response 响应对象
     */
    public void print(HttpUriRequest request, HttpResponse response) {
        try {
            StringBuilder resultSb = new StringBuilder("\n");

            resultSb.append(String.format("URL:%s\n", request.getURI()));

            resultSb.append(String.format("Method:%s\n", request.getMethod()));

            Header[] allHeaders = request.getAllHeaders();
            resultSb.append("RequestHeaders:\n");
            for (Header allHeader : allHeaders) {
                resultSb.append(String.format("\t%s:%s\n", allHeader.getName(), allHeader.getValue()));
            }

            if (EmptyUtil.isNotEmpty(cookieStore)) {
                List<Cookie> cookies = cookieStore.getCookies();
                resultSb.append("RequestCookies:\n");
                for (Cookie cookie : cookies) {
                    resultSb.append(String.format("\t%s:%s\n", cookie.getName(), cookie.getValue()));
                }
            }

            resultSb.append("requestEntity:\n");
            if (EmptyUtil.isNotEmpty(bodyEntity)) {
                resultSb.append("\t").append(FileUtil.readToString(bodyEntity.getContent())).append("\n");
            }

            resultSb.append(String.format("ResponseCode:%s\n", response.getStatusLine().getStatusCode()));

            resultSb.append("ResponseHeaders:\n");
            for (Header allHeader : response.getAllHeaders()) {
                resultSb.append(String.format("\t%s:%s\n", allHeader.getName(), allHeader.getValue()));
            }

            resultSb.append("ResponseEntity:\n");
            resultSb.append(responseHandler.buildPrintBody());

            if (printLog) {
                log.debug(resultSb.toString());
            }

            if (printFile) {
                printFilePath = printFilePath.endsWith("/") || printFilePath.endsWith("\\") ? printFilePath :
                        printFilePath + File.separator;
                String fileName =
                        URLEncoder.encode(request.getURI().getHost(), "UTF-8") + "-" + request.getMethod() + "-" + URLEncoder.encode(request.getURI().getPath(), "UTF-8") + "-" + DateUtil.getNowTimestamp() + ".req";
                FileUtil.write(printFilePath + fileName, resultSb.toString());
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public RequestConfig getRequestConfig() {
        return requestConfig;
    }

    public HttpRequester setRequestConfig(RequestConfig requestConfig) {
        this.requestConfig = requestConfig;
        return this;
    }

    public HttpClient getHttpClient() {
        return httpClient;
    }

    public HttpRequester setHttpClient(HttpClient httpClient) {
        this.httpClient = httpClient;
        return this;
    }

    public String getHttpMethod() {
        return httpMethod;
    }

    public HttpRequester setHttpMethod(String httpMethod) {
        this.httpMethod = httpMethod;
        return this;
    }

    public String getUrl() {
        return url;
    }

    public HttpRequester setUrl(String url) {
        this.url = url;
        return this;
    }

    public Map<String, String> getQueryParamMap() {
        return queryParamMap;
    }

    public HttpRequester setQueryParamMap(Map<String, String> queryParamMap) {
        this.queryParamMap = queryParamMap;
        return this;
    }

    public Map<String, String> getHeaderMap() {
        return headerMap;
    }

    public HttpRequester setHeaderMap(Map<String, String> headerMap) {
        this.headerMap = headerMap;
        return this;
    }

    public HttpEntity getBodyEntity() {
        return bodyEntity;
    }

    public HttpRequester setBodyEntity(HttpEntity bodyEntity) {
        this.bodyEntity = bodyEntity;
        return this;
    }

    public ResponseHandler<? extends HttpResponseResult> getResponseHandler() {
        return responseHandler;
    }

    public HttpRequester setResponseHandler(ResponseHandler<? extends HttpResponseResult> responseHandler) {
        if (EmptyUtil.isNotEmpty(responseHandler)) {
            this.responseHandler = responseHandler;
        }
        return this;
    }

    public Boolean getPrintLog() {
        return printLog;
    }

    public HttpRequester setPrintLog(Boolean printLog) {
        this.printLog = printLog;
        return this;
    }

    public Boolean getPrintFile() {
        return printFile;
    }

    public HttpRequester setPrintFile(Boolean printFile) {
        this.printFile = printFile;
        return this;
    }

    public String getPrintFilePath() {
        return printFilePath;
    }

    public HttpRequester setPrintFilePath(String printFilePath) {
        this.printFilePath = printFilePath;
        return this;
    }
}
